package com.fkdx.webman.result;

import com.alibaba.fastjson.JSONObject;
import com.fkdx.common.enums.sys.ErrCodeEnum;
import com.fkdx.common.enums.sys.GroupException;
import lombok.extern.log4j.Log4j2;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 全局异常处理类
 * 统一异常处理，将服务抛出的异常统一处理为标准响应体
 * Controller、Service 有问题只需要抛异常，无需处理错误响应体
 * <p>
 * 为了兼容原有标库的 controller，该异常处理只对 assignableTypes 指定的 controller 生效
 *
 * @author liujia05
 */
@Log4j2
@ControllerAdvice
// 不加@RestControllerAdvice注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
public class ResultExceptionHandler {

  @ExceptionHandler
  // 不加@ResponseBody注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
  public String exceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, Exception e) {
    Result<Void> result = new Result<>(ErrCodeEnum.SERVER_ERROR);
    log.error("exception handler", e);
    return switchView(request, response, model, result);
  }

  @ExceptionHandler
  // 不加@ResponseBody注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
  public String illegalArgumentExceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, IllegalArgumentException e) {
    Result<Void> result = new Result<>(ErrCodeEnum.PARAM_ERROR.getErrCode(), e.getMessage());
    return switchView(request, response, model, result);
  }

  @ExceptionHandler
  // 不加@ResponseBody注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
  public String groupExceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, GroupException e) {
    Result<Void> result = new Result<>(e.getErrCode(), e.getErrMsg());
    return switchView(request, response, model, result);
  }

  @ExceptionHandler
  // 不加@ResponseBody注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
  public String notValidExceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, MethodArgumentNotValidException e) {
    Result<Void> result = new Result<>(ErrCodeEnum.PARAM_ERROR);
    return switchView(request, response, model, result);
  }

  @ExceptionHandler
  // 不加@ResponseBody注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
  public String bindExceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, BindException e) {
    FieldError fieldError = e.getFieldError();
    if (fieldError != null) {
      String fieldName = fieldError.getField();
      String fieldValue = JSONObject.toJSONString(fieldError.getRejectedValue());
      String fieldMessage = fieldError.getDefaultMessage();
      Result<Void> result = new Result<>(ErrCodeEnum.PARAM_ERROR, fieldName, fieldValue, fieldMessage);
      return switchView(request, response, model, result);
    }
    return switchView(request, response, model, new Result<>(ErrCodeEnum.PARAM_ERROR));
  }

  @ExceptionHandler
  // 不加@ResponseBody注解，Ajax请求返回错误信息，非Ajax请求跳转到无权限提示页面
  public String accessDeniedExceptionHandler(HttpServletRequest request, HttpServletResponse response, Model model, org.springframework.security.access.AccessDeniedException e) throws IOException {
    Result<Void> result = new Result<>(ErrCodeEnum.PERM_ERROR);
    return switchView(request, response, model, result);
  }

  private String switchView(HttpServletRequest request, HttpServletResponse response, Model model, Result<Void> result) {
    String requestedWith = request.getHeader("x-requested-with");
    if (requestedWith != null && requestedWith.equals("XMLHttpRequest")) {
      response.setContentType("application/json;charset=UTF-8");
      PrintWriter out = null;
      try {
        out = response.getWriter();
        out.println(JSONObject.toJSONString(result));
        return null;
      } catch (IOException e) {
        log.error("全局错误选择视图时发生错误", e);
      } finally {
        Assert.notNull(out, "输出错误时无法获取输出流");
        out.flush();
        out.close();
      }
      return JSONObject.toJSONString(result);
    }
    model.addAttribute("error", result);
    return "500";
  }

}
